package com.supplychain.scm.core.dinger.aspect;

import com.supplychain.scm.core.dinger.annotation.DingerTalk;
import com.supplychain.scm.core.dinger.util.DingDingUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author liyang
 * @version 1.0.0
 * @ClassName DingdingTalkAspect
 * @createTime 19:33:00 2022/11/18
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class DingdingTalkAspect {

	private final ApplicationContext applicationContext;

	@Pointcut("@annotation(com.supplychain.scm.core.dinger.annotation.DingerTalk)")
	public void pointcut() {

	}

	@Around("pointcut()")
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		// 获取注解
		DingerTalk dingTalk = AnnotationUtils.findAnnotation(getSpecificmethod(proceedingJoinPoint), DingerTalk.class);
		String className;
		if(proceedingJoinPoint.getSignature() instanceof MethodSignature) {
			className = String.format("Class Method   : %s#%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), ((MethodSignature)proceedingJoinPoint.getSignature()).getMethod().getName());
		} else {
			className = String.format("Class Method   : %s#%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
		}
		Object result = null;
		try {
			// 开始时间
			long stime = System.currentTimeMillis();
			result = proceedingJoinPoint.proceed();
			// 结束时间
			long etime = System.currentTimeMillis();
			log.info(className.concat(" 执行时间(毫秒)：").concat(String.valueOf(etime - stime)));
		} catch (Throwable e) {
			if(this.containForCheck(dingTalk,e) && this.noContainForCheck(dingTalk,e)) {
				if(!this.onMessageForCheck(dingTalk,e) && !this.onMessageCodeForCheck(dingTalk,e)) {
					Environment environment = applicationContext.getEnvironment();
					String active = environment.getActiveProfiles()[0];
					Object[] args = proceedingJoinPoint.getArgs();
					if(StringUtils.hasText(dingTalk.title())) {
						DingDingUtils.sendMarkdownMsg(DingDingUtils.dingExceptionMsg(e,className,args,dingTalk,active),dingTalk.title(),dingTalk.properties(),dingTalk.defaultRoot());
					} else {
						DingDingUtils.sendMarkdownMsg(DingDingUtils.dingExceptionMsg(e,className,args,dingTalk,active),dingTalk.properties(),dingTalk.defaultRoot());
					}
				}
			}
			log.warn("钉钉消息捕获异常 ex => {}",e);
			throw e;
		}
		return result;
	}

	private boolean noContainForCheck(DingerTalk dingTalk,Throwable e) {
		if(Objects.isNull(dingTalk.noContainFor()) || dingTalk.noContainFor().length == 0) {
			return Boolean.TRUE;
		}
		boolean flag = Boolean.TRUE;
		for (Class<? extends Throwable> throwaleClass : dingTalk.noContainFor()) {
			flag = throwaleClass.isAssignableFrom(e.getClass());
			if(flag){
				return Boolean.FALSE;
			}
		}
		return flag;
	}

	private boolean containForCheck(DingerTalk dingTalk,Throwable e){
		if(Objects.isNull(dingTalk.containFor()) || dingTalk.containFor().length == 0) {
			return Boolean.TRUE;
		}
		boolean flag = Boolean.FALSE;
		for (Class<? extends Throwable> throwaleClass : dingTalk.containFor()) {
			flag = throwaleClass.isAssignableFrom(e.getClass());
			if(flag){
				return flag;
			}
		}
		return flag;
	}

	private boolean onMessageForCheck(DingerTalk dingTalk,Throwable e) {
		if(Objects.isNull(dingTalk.noMessageFor()) || dingTalk.noMessageFor().length == 0) {
			return false;
		}
		boolean flag = false;
		for (String message : dingTalk.noMessageFor()) {
			flag = Objects.equals(message,e.getMessage());
			if(flag){
				return flag;
			}
		}
		return flag;
	}

	private boolean onMessageCodeForCheck(DingerTalk dingTalk,Throwable e) {
		boolean flag = false;
		if(Objects.isNull(dingTalk.noMessageCodeFor()) || dingTalk.noMessageCodeFor().length == 0) {
			return flag;
		}
		Class<? extends Throwable> exceptionClass = e.getClass();
		Object code = null;
		try {
			code = this.getMessageCodeValue(dingTalk,exceptionClass,e);
		} catch (IllegalAccessException ex) {
			log.warn("DingdingTalkAspect#onMessageCodeForCheck error:",ex);
		}
		if(Objects.isNull(code)) {
			return flag;
		}
		for (String messageCode : dingTalk.noMessageCodeFor()) {
			flag = Objects.equals(messageCode,code.toString());
			if(flag){
				return flag;
			}
		}
		return flag;
	}

	private Object getMessageCodeValue(DingerTalk dingTalk,Class<? extends Throwable> clazz,Throwable e) throws IllegalAccessException {
		Field[] fields = clazz.getDeclaredFields();
		Field fieldMessageCode = null;
		if(Objects.nonNull(fields) && fields.length > 0) {
			for (Field field : fields) {
				if(Objects.equals(field.getName(),dingTalk.messageCodeFieldName())) {
					fieldMessageCode = field;
					break;
				}
			}
		}
		if(Objects.isNull(fieldMessageCode)) {
			Class<?> superclass = clazz.getSuperclass();
			if (Objects.isNull(superclass)) {
				return null;
			}
			return this.getMessageCodeValue(dingTalk, (Class<? extends Throwable>) superclass,e);
		} else {
			fieldMessageCode.setAccessible(true);
			return fieldMessageCode.get(e);
		}
	}

	private Method getSpecificmethod(ProceedingJoinPoint pjp) {
		MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
		Method method = methodSignature.getMethod();
		// The method may be on an interface, but we need attributes from the
		// target class. If the target class is null, the method will be
		// unchanged.
		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
		if (targetClass == null && pjp.getTarget() != null) {
			targetClass = pjp.getTarget().getClass();
		}
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
		// If we are dealing with method with generic parameters, find the
		// original method.
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
		return specificMethod;
	}
}
